/* Since there's nothing special about this object it really ought to
 * be written in Kew, but I haven't gotten around to working on the
 * mechanism for this to happen seamlessly yet.  Maybe I could use the
 * existing dynamic link stuff. */

#include <stdio.h> 
#include <kewinternal.h>

#define SOURCE_FILE_NAME "Range"

#define from_value(OBJECT) ((OBJECT)->context[0])
#define to_value(OBJECT) ((OBJECT)->context[1])
#define check_range(VAL) KewCheckType((VAL), &KewRangeType, "range")


METHOD(Hash)
{
  /* FIXME: Rather weak combination hash */
  KewObject *from_hash = KewSend0(from_value(receiver), "hash");
  KewObject *to_hash = KewSend0(to_value(receiver), "hash");
  results[0] = KewSend1(from_hash, "+", to_hash);
}


METHOD(Equal)
{
  KewObject *from_equal;
  KewObject *to_equal;

  if (args[0]->type != &KewRangeType) {
    results[0] = KewFalse;
    return;
  }

  from_equal = KewSend1(from_value(receiver), "=", from_value(args[0]));
  to_equal = KewSend1(to_value(receiver), "=", to_value(args[0]));
  results[0] = KewNewBool(KewIsTrue(from_equal) && KewIsTrue(to_equal));
}


METHOD(First)
{
  results[0] = from_value(receiver);
}


METHOD(Last)
{
  results[0] = to_value(receiver);
}


METHOD(RangeFrom)
{
  KewObject *new_from = args[0];
  KewObject *range_size = KewSend1(to_value(receiver), "-", from_value(receiver));
  KewObject *new_to = KewSend1(new_from, "+", range_size);
  results[0] = KewNewRange(new_from, new_to);
}

METHOD(Size)
{
  int size = KewIntVal(to_value(receiver)) + 1 - KewIntVal(from_value(receiver));
  results[0] = size < 0 ? KewNewInt(0) : KewNewInt(size);
}

METHOD(Range)
{
  results[0] = receiver;
}

METHOD(Reader_Reader)
{
  results[0] = receiver;
}

METHOD(Reader_Read)
{
  /* FIXME: raise an exception if the reader is empty */
  results[0] = from_value(receiver);
  from_value(receiver) = KewSend1(from_value(receiver), "+", KewNewInt(1));
}

METHOD(Reader_Empty)
{
  results[0] = KewSend1(from_value(receiver), ">", to_value(receiver));
}

METHOD(Reader_List)
{
  results[0] = KewNewRange(from_value(receiver), to_value(receiver));
}


static KewObjectType range_reader_type = {
  SOURCE_FILE_NAME, __LINE__,
  2,
  {
    {"reader", Reader_Reader, 0, 1},
    {"read", Reader_Read, 0, 1},
    {"empty", Reader_Empty, 0, 1},
    {"list", Reader_List, 0, 1},
    {NULL, NULL, 0, 0}
  }
};

METHOD(Reader)
{
  results[0] = KewNewObject(&range_reader_type);
  from_value(results[0]) = from_value(receiver);
  to_value(results[0]) = to_value(receiver);
}

KewObject *
KewRangeFor(KewObject *range, KewObject *initial, KewObject *block)
{
  KewObject *accumulator;
  KewObject *current;
  KewObject *target;
  KewObject *increment;

  check_range(range);
  accumulator = initial;
  current = from_value(range);
  target = to_value(range);
  increment = KewNewInt(1);

  while (KewIsTrue(KewSend1(current, "<=", target))) {
    accumulator = KewSend2(block, "do", accumulator, current);
    current = KewSend1(current, "+", increment);
  }

  return accumulator;
}


KewObject *
KewNewRange(KewObject *from, KewObject *to)
{
  KewObject *result;

  result = KewNewObject(&KewRangeType);
  from_value(result) = from;
  to_value(result) = to;
  return result;
}


KewObjectType KewRangeType = {
  SOURCE_FILE_NAME, __LINE__,
  2,
  {
    {"hash", Hash, 0, 1},
    {"=", Equal, 1, 1},
    {"first", First, 0, 1},
    {"last", Last, 0, 1},
    {"rangeFrom", RangeFrom, 1, 1},
    {"size", Size, 0, 1},
    {"range", Range, 0, 1},
    {"reader", Reader, 0, 1},
    {NULL, NULL, 0, 0}
  }
};
